Skip to content

feat(smartlead): add Smartlead integration - #6352

Merged
waleedlatif1 merged 9 commits into
stagingfrom
worktree-smartlead-integration
Aug 7, 2026
Merged

feat(smartlead): add Smartlead integration#6352
waleedlatif1 merged 9 commits into
stagingfrom
worktree-smartlead-integration

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a Smartlead block with 47 tools covering campaigns, sequences, leads, email accounts, analytics, the master inbox, lead lists, and webhooks
  • Every path, parameter, enum, and response mapping was verified against the live API rather than the docs — Smartlead's published response bodies are substantially wrong
  • Adds a smartlead-errors extractor, since the API's 400s put the useful text in message while error is only "Bad Request"

Why not the docs

The docs contradict themselves and the API. The /statistics page's prose describes paginated per-email rows while its own example JSON shows a single aggregate object; the live endpoint returns rows. Building against the examples would have produced tools that return null for nearly every output while looking fine in review.

Corrected against the live API:

  • POST /campaigns/new 404s — the real path is /campaigns/create
  • GET /campaigns/{id} and /sequences return bare payloads, not {success, data} envelopes
  • add-leads returns {upload_count, total_leads, duplicate_count, …}, not {added_count, skipped_count}
  • GET /leads/{id} is not under /campaigns/ and wraps the lead in a single-element array
  • lead lists are /lead-list/ (singular); /crm/leads/tags 404s entirely
  • webhook delete takes the id in the body, not the path
  • DELETE .../leads/{id} answers with the bare text success, not JSON

Enum values were probed one at a time — EMAIL_OPENED/EMAIL_CLICKED/EMAIL_REPLIED are all rejected; the real values are EMAIL_OPEN/EMAIL_LINK_CLICK/EMAIL_REPLY.

Quirks encoded in the mappers: string-encoded numbers are normalized so a field never changes type between operations; seq_delay_details reads delayInDays but writes delay_in_days; webhook writes echo event_type_map objects while reads return event_types arrays; track_settings reads back in a vocabulary it will not accept on write.

Several lookups answer HTTP 200 with {} for a missing resource instead of 404, so those tools fail closed rather than reporting an all-null success.

Not included, deliberately

  • Smart Delivery, Smart Prospect, Smart Senders and the agency Analytics family (~84 of the 190 endpoints) — separate product surfaces
  • A webhook trigger, and the reply/forward tools — they need a connected mailbox, which cannot be created on a test account, so no payload could be observed
  • add/remove email accounts to a campaign are the only tools whose success shape is unverified for the same reason; their error path is verified
  • Rows for statistics, message history, inbox replies, email accounts and clients pass through unmapped — those collections were empty, so no field names were invented

Type of Change

  • New feature

Testing

All 47 tools executed end-to-end against the live Smartlead API through their real ToolConfigs. Three rounds of validation agents over the tool layer, block layer, and registry/artifacts; every finding fixed and re-verified live. Test account left empty.

Passing: typecheck, lint, check:api-validation, tool-metadata:check, integration-catalog:check, check:bare-icons, and the error-extractor tests (verified they fail without the fix).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Adds a Smartlead block with 22 tools covering campaigns, sequences, leads,
analytics, and webhooks.

Every request path, parameter, enum, and response mapping was verified against
the live Smartlead API rather than its documentation, which proved unreliable:

- `POST /campaigns/new` (documented) 404s; the real path is `/campaigns/create`
- `GET /campaigns/{id}` and `/sequences` return bare payloads, not the
  documented `{success, data}` envelopes
- `/statistics` returns paginated per-email rows, not the documented aggregate
- `POST /campaigns/{id}/leads` returns import counters under entirely
  different field names than documented
- documented `/leads/{id}`, `/top-level-analytics`, `/all-leads-activities`,
  `/lead-lists/`, and `/lead-tags/` all 404

Enum values (campaign status, track settings, stop-lead settings, webhook event
types, engagement status) were probed value-by-value against the API.

Notes on the API's shape, encoded in the mappers:
- string-encoded numbers (`total_leads: "1"`, `sent_count: "0"`) are normalized
  to numbers so a field never changes type between operations
- `seq_delay_details` is read as `delayInDays` but written as `delay_in_days`
- webhook writes echo `event_type_map`/`category_id_map` objects while the list
  endpoint returns `event_types`/`categories` arrays; both map to arrays
- `track_settings` reads back in a vocabulary it will not accept on write

Statistics rows and lead message-history entries pass through unmapped: no
account could produce a non-empty sample, so no field names were invented.
Email-account tools and a webhook trigger are omitted for the same reason.

Adds a `smartlead-errors` extractor since the API's 400s put the useful text in
`message` while `error` is only "Bad Request".
…indings

Grows the block from 22 to 47 tools and fixes every defect found in review.

New tools (all executed against the live API end to end):
campaign email accounts (list/add/remove), duplicate, delete, CSV lead export,
webhook delete + delivery summary, lead + mailbox statistics, top-level
analytics by date, lead activities, get lead by id, unsubscribe from campaign,
unsubscribe globally, mark complete, delete from campaign, master-inbox
replies, lead lists (list/get/create/update/delete), email accounts, clients.

The endpoint inventory was rebuilt by extracting method+path from all 212
reference pages, which corrected several earlier conclusions: get-lead-by-id is
`/leads/{id}` (not under `/campaigns/`), lead lists are `/lead-list/`
(singular), and lead activities are `/campaigns/all-leads-activities` with no
campaign segment. More documented paths that 404 in reality: lead tags at
`/crm/leads/tags`, and webhook delete at `/campaigns/{id}/webhooks/{id}` —
deletion actually takes the id in the body.

Shapes the docs got wrong again, caught live: `GET /leads/{id}` wraps the lead
in a single-element `data` array; `DELETE .../leads/{id}` answers with the bare
string `success`, not JSON; duplicate returns `newCampaignId`; create/update
lead list take `listName`, and mark-complete takes `campaign_lead_map_id` where
its siblings take `lead.id`.

Review fixes:
- get_campaign, get_campaign_analytics and get_lead_by_email reported an
  all-null success for a missing resource, because Smartlead answers HTTP 200
  with `{}` (or an empty body) instead of 404. They now fail closed.
- update_campaign_settings silently reset stop_lead_settings and
  send_as_plain_text: their dropdown defaults are materialized at block
  creation, so every settings update carried them. Both now default to
  "Leave unchanged".
- Malformed JSON in Leads/Sequences/Custom Fields resolved to `undefined`,
  which overwrote the raw string the executor falls back on and dropped the
  field silently. Parsing now raises, and is scoped to the operation that
  consumes the field so a stale hidden value cannot fail an unrelated one.
- The four documented import overrides (block/unsubscribe/duplicate/bounce
  lists) had no field, so the block's own skill instructions were unexecutable.
- leadId did not distinguish lead.id from campaign_lead_map_id; passing the
  latter 404s, and list_campaign_leads surfaces it first.
- Path ids are trimmed and escaped; dead code and a hand-rolled id mapper removed.

Unverified and called out rather than guessed: add/remove email accounts to a
campaign (no mailbox could be connected, so only their error shape was seen),
and the row shapes for statistics, message history, inbox replies, email
accounts and clients — every one of those collections was empty on the
verification account, so their rows pass through unmapped.
…tion

Three tools sent a parameter Smartlead's validator rejects outright with 400,
so the affected operations failed whenever the field was filled in:

- get_campaign_lead_statistics paginated with `skip`; the endpoint accepts
  `offset` and only echoes it back as `skip`.
- list_lead_activities and list_inbox_replies both sent a campaign filter.
  `campaign_id`, `campaignId`, `campaign_ids` and `email_campaign_id` are all
  rejected, so the filter is gone rather than advertised and broken.

mark_lead_complete reported `next_sequence: null` on every call, including when
a step remained: `status.nextSequence` is an object, not a number. It now maps
to `next_sequence_id` and `next_sequence_delay_in_days` — verified live
returning step 10093171 rather than null.

get_lead_by_id reused the by-email mapper, so it always claimed the lead belongs
to zero campaigns; `GET /leads/{id}` omits `lead_campaign_data` entirely. It now
declares the narrower shape it actually returns.

A stale advanced `clientId` leaked into list_email_accounts: advanced subblocks
serialize without evaluating their condition, and that tool consumes `clientId`
while sitting outside its condition list. The field is now offered for that
operation too, so the value is visible wherever it is sent.

Two dropdowns had defaults that act on their own. `status` defaulted to PAUSED,
so choosing Update Campaign Status and never opening the dropdown paused the
campaign; it now requires an explicit choice. `pauseLead` sent `false` on every
categorization, which risks resuming a paused lead; it now defaults to leaving
the state alone.

Also counts CSV export rows with a quote-aware scan so a newline inside a name,
location, or custom field no longer inflates the count, and fills in the block
output declarations for the fields the 47 tools actually return.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Error Error Aug 7, 2026 12:35am

Request Review

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large new third-party API surface with many tools and non-obvious response shapes; mistakes could cause silent bad outputs or failed workflows, though it does not touch core auth or payment paths.

Overview
Adds a Smartlead workflow block with 47 tools for campaigns, sequences, leads, mailboxes, analytics, master inbox, lead lists, and webhooks, shaped against the live API (paths, envelopes, enums, and field naming) rather than published docs that often disagree with production.

Includes a smartlead-errors extractor so 400 responses surface useful text from message, plus mappers for API quirks (string numbers, read/write field differences, empty {} “missing” responses handled fail-closed). Several product areas and mailbox-dependent tools are intentionally out of scope.

The diff shown wires docs/catalog: a branded SmartleadIcon, smartlead in icon-mapping, and the integrations meta entry (alongside the integration doc page).

Reviewed by Cursor Bugbot for commit d7662d9. Configure here.

…-integration

# Conflicts:
#	apps/sim/tools/generated/tool-ids.ts
#	apps/sim/tools/generated/tool-metadata.ts
#	apps/sim/tools/generated/tool-outputs.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a Smartlead integration spanning campaign, sequence, lead, account, analytics, inbox, lead-list, and webhook operations.

  • Registers the Smartlead block, tools, icon, integration metadata, and generated tool artifacts.
  • Adds Smartlead-specific request/response normalization and API error extraction.
  • Adds public integration documentation for the available operations and contracts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/blocks/blocks/smartlead.ts Defines the Smartlead block’s operation selector, input controls, parameter transformations, and workflow output contract.
apps/sim/tools/smartlead/utils.ts Centralizes Smartlead constants and response normalization, including nullable numeric conversion.
apps/sim/tools/smartlead/mark_lead_complete.ts Correctly preserves zero-day next-sequence delays while normalizing absent or invalid values to null.
apps/sim/tools/error-extractors.ts Adds extraction of useful Smartlead error messages from API error responses.
apps/docs/content/docs/en/integrations/smartlead.mdx Documents the Smartlead operations with concrete enum values and generated input/output contracts.
apps/sim/tools/registry.ts Registers the new Smartlead tools for execution.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Workflow[Workflow Smartlead block] --> Mapper[Operation selector and parameter mapper]
  Mapper --> Registry[Smartlead tool registry]
  Registry --> Tool[Selected Smartlead ToolConfig]
  Tool --> API[Smartlead API]
  API --> Normalize[Response and error normalization]
  Normalize --> Outputs[Workflow outputs]
Loading

Reviews (6): Last reviewed commit: "fix(smartlead): remove the dead campaign..." | Re-trigger Greptile

Comment thread apps/sim/tools/smartlead/mark_lead_complete.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 9aa6963. Configure here.

Comment thread apps/sim/blocks/blocks/smartlead.ts
Comment thread apps/docs/content/docs/en/integrations/smartlead.mdx Outdated
…um values in docs

A next sequence scheduled to send immediately reported no delay at all:
`Number(next.delayInDays) || null` mapped a legitimate 0 to null.

Tool descriptions built enum lists with template literals. The runtime value
and the LLM-facing tool metadata were correct, but the docs generator reads the
description statically, so the public page rendered
`${SMARTLEAD_CAMPAIGN_STATUSES.join(...)}` instead of START, PAUSED, STOPPED.
The five affected descriptions now spell the values out.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

…-integration

# Conflicts:
#	apps/sim/tools/generated/tool-metadata.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d1c543d. Configure here.

…tials

Connecting a real mailbox to the verification account made the email-account
response shapes observable for the first time, and they carry the stored
credentials: `GET /email-accounts/{id}/` and the campaign route return
`password` in plaintext, the list route returns it base64-encoded, and both
carry `imap_password`.

Both tools passed rows through unmapped, so those values would have reached
workflow output, execution logs, and model context. They now select fields
explicitly and omit the credentials.

Verified against the live API: the API response contains the password while the
tool output does not, for both tools.

Also fills in the real email-account fields, which were previously an opaque
array — id, sender identity, SMTP/IMAP host and port, verification state and
last error, sending caps, warmup status, and tags.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0548ef6. Configure here.

…wrong campaign

Removing the campaign filters from list_lead_activities and list_inbox_replies
left their `activityCampaignId` subblock, its params mapping, and its inputs
entry behind. Two problems, the second serious:

- On those two operations the field promised campaign scoping the API cannot
  do. Smartlead rejects every candidate key (`campaign_id`, `campaignId`,
  `campaign_ids`, `email_campaign_id`), so the value was silently discarded and
  account-wide results were reported as scoped.
- Worse, the field is `mode: 'advanced'`, and advanced subblocks serialize
  without evaluating their condition. A value left over from listing activities
  therefore fed `campaignId` on all 32 campaign operations through the
  `params.campaignId || params.activityCampaignId` fallback. Configuring List
  Lead Activities with campaign 111, then switching the block to Delete
  Campaign and leaving Campaign ID blank, would have passed required-validation
  and deleted campaign 111.

Both list tools now also say plainly that Smartlead exposes no campaign filter,
rather than advertising one in their descriptions.

Also: route mark_lead_complete's next-sequence id through the shared numeric
coercion, since Smartlead string-encodes numbers inconsistently and its sibling
field already arrives as a string; re-bind the two enum constants that lost
their last consumer so the literal descriptions cannot drift undetected; and
declare the 17 tool output keys the block was missing — `accounts` most
importantly, which is the entire payload of both email-account tools.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d7662d9. Configure here.

@waleedlatif1
waleedlatif1 enabled auto-merge (squash) August 7, 2026 00:40
@waleedlatif1
waleedlatif1 merged commit aae9ce6 into staging Aug 7, 2026
23 of 24 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-smartlead-integration branch August 7, 2026 00:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant